package com.fengzai.myproxy;

import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Created by  峰仔  on 2017-08-03
 */
public class MyProxy {
    public static Object newProxyInstance(MyClassLoader loader, Class<?>[] interfaces, MyInvocationHandler h) {

        String path = MyProxy.class.getResource("").getPath();
        File file = new File(path + "$Proxy0.java");

        //1、生成源代码
        String prosySrc = generateSrc(interfaces);
        //2、把生成的源代码保存到磁盘
        try (FileWriter fw = new FileWriter(file)) {
            fw.write(prosySrc);
            fw.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        //3、编译源代码
        JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager manager = javaCompiler.getStandardFileManager(null, null, null);
        Iterable<? extends JavaFileObject> iterable = manager.getJavaFileObjects(file);
        CompilationTask task = javaCompiler.getTask(null, manager, null, null, null, iterable);
        task.call();
        try {
            manager.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        Class<?> proxy = null;
        try {
            //4、加载.class到内存
            proxy = loader.findClass("$Proxy0");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        Constructor<?> constructor = null;
        try {
            assert proxy != null;
            constructor = proxy.getConstructor(MyInvocationHandler.class);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        try {
            assert constructor != null;
            //5、取出对象返回
            return constructor.newInstance(h);
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        } finally {
            //将生成的.java和.class删除  消除痕迹   做到无感知代理
            file.delete();

        }
        return null;

    }

    private static String generateSrc(Class<?>[] interfaces) {
        String ln = "\r\n";
        StringBuffer sbf = new StringBuffer();
        sbf.append("package com.fengzai.myproxy;" + ln + ln);
        sbf.append("import java.lang.reflect.UndeclaredThrowableException;" + ln + ln);
        sbf.append("public class $Proxy0 implements ");
        for (int i = 0; i < interfaces.length; i++) {
            sbf.append(interfaces[i].getName());
            if (i < interfaces.length - 1) {
                sbf.append(", ");
            }
        }
        sbf.append(" {" + ln);
        sbf.append("    private MyInvocationHandler h;" + ln + ln);
        sbf.append("    public $Proxy0(MyInvocationHandler h) {" + ln);
        sbf.append("        this.h = h;" + ln);
        sbf.append("    }" + ln + ln);
        for (int i = 0; i < interfaces.length; i++) {
            Method[] methods = interfaces[i].getMethods();
            for (Method method : methods) {
                sbf.append("    public " + method.getReturnType().getName() + " " + method.getName() + "(");
                Class<?>[] parameterTypes = method.getParameterTypes();
                for (int j = 0; j < parameterTypes.length; j++) {
                    sbf.append(parameterTypes[j].getName() + " arg" + j);
                    if (j < parameterTypes.length - 1) {
                        sbf.append(", ");
                    }
                }
                sbf.append(") {" + ln);
                sbf.append("        Object obj = null;" + ln);
                sbf.append("        try {\n");

                sbf.append("            obj = this.h.invoke(this, " + interfaces[i].getName() + ".class.getMethod(\"" + method.getName() + "\", new Class[");
                if (parameterTypes.length == 0) {
                    sbf.append("0");
                }
                sbf.append("]");
                if (parameterTypes.length > 0) {
                    sbf.append("{");
                    for (int j = 0; j < parameterTypes.length; j++) {
                        sbf.append("Class.forName(\"");
                        if (j == 0) {
                            sbf.append(parameterTypes[j].getName());

                        } else

                            sbf.append("," + parameterTypes[j].getName());
                        sbf.append("\")");
                    }
                    sbf.append("}");
                }
                sbf.append("),");
                if (parameterTypes.length > 0) {
                    sbf.append(" new Object[]{");
                    for (int j = 0; j < parameterTypes.length; j++) {
                        if (j == 0) {
                            sbf.append("arg" + j);
                            continue;
                        }
                        sbf.append(", arg" + j);
                    }
                    sbf.append("}");
                } else {
                    sbf.append("null");
                }
                sbf.append(");" + ln);
                sbf.append("        } catch (RuntimeException localRuntimeException) {\n" +
                        "            throw localRuntimeException;\n" +
                        "        } catch (Throwable localThrowable) {\n" +
                        "            throw new UndeclaredThrowableException(localThrowable);\n" +
                        "        }\n");

                if (!"void".equals(method.getReturnType().getName())) {
                    sbf.append("        return (" + method.getReturnType().getName() + ") obj;" + ln);
                }
                sbf.append("    }" + ln);
            }
        }
        sbf.append("}");
        return sbf.toString();
    }
}
